<?php
/*
    HerbalWorkshop. Web database of herbals and oils, recipes and their effects.
    Copyright (C) 2009  Nikolay Gniteev godhart@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

// Here would be class for parsing form description
// Result of parse is array with following content:
//		{template,template_variables}

  class Form_Parser {
  		
  		public $Template = array();
  		public $Aliases = array();
  		public $MissingAliases = array();
  		public $Debug_Unparsed = array();
  		public $Debug_Parsed = array();
  		
  		const quoted_regex = '(?x:				#enable extended regex (comments)
							(?<qouted>							
							(?:\w+)					#only word characters
							|(?:						#or quoted string
								(?<quotes>["\'`~])# opening quotes
								.+?					#  string
								(?<!\\\\)			# unescaped
								\g{quotes}	   	#  closing quotes (SAME as opening)
							 )
							|(?:						#or braced string
								(?:					# opening braces
									 (?<squared>\[)
									|(?<angled> \<)
									|(?<curly>  \{)
								)
								.+?					#  string
							 	
							 	(?<!\\\\)			# unescaped
							 	(?: 					#  closing braces
							 	 (?(<squared>) \])
							 	 (?(<angled>)  \>)
							 	 (?(<curly>)   \})
							 	)
							 )
						))';
						
		const parens_regex = '(?:x							#enable extended regex (comments)
									 (               			# paren group 1 (whole expression)     
                            (                  		# paren group 2 (recursive parens)
                                \(
                                    (              # paren group 3 (contents of parens)
                                    (?:
                                      (?> [^()]+ ) # Non-parens without backtracking
                                    |
                                      (?2)         # Recurse to start of paren group 2
                                    )*
                                    )
                                \)
                            )
                        ));';
						
  		function Form_Parser($file_name='')
  		{
  			//creates class and loads form template from file
  			if (!$file_name) return;
  			if (!($file = @fopen($file_name,'r'))) return; 
  			while (!feof($file))
  			{
  				$line = fgets($file);
				$parse_result = $this->Parse($line);
				if ($parse_result)
				{ $this->Template[] = $parse_result;}
  			}
  			fclose($file);
  		}
  		  		
  		function Load($strings)
  		{
  			//loads template from variable
  			
  			//check if it's array or not. convert into array if necessary
			if (!is_array($strings))
			{
				$strings = (array)$strings;
			}
  			
  			//foreach
  			foreach ($strings as $string)
  			{
  				//split string with '\n'
  				$lines = split("[\n]", $string);
  				//parse each line
  				foreach ($lines as $line)
  				{
  					$parse_result = $this->Parse($line);
  					$this->Template[] = $parse_result;
  				}
  			}
  		}
  		
  		function Evaluate()
  		{
  			//return parsed template
  		}
  		
  		function Connect($db_engines)
  		{
  			//connects with databases (needs to get information about fields like type,size)
  		}
  		
  		function Disconnect($db_engines)
  		{
  			//Disconnects from databases
  		}
  		
  		function Parse_Arguments($line, &$arg, &$full_match, $delimeter='\s', $recurent_delimeter=',')
  		{
  			$arg='';
  			$full_match='';
  			
  			$parens_regex='/^\s*'.self::parens_regex."(?:(?:$)|(?:$delimeter)/";
  			$quoted_regex='/^\s*'.self::quoted_regex."(?:(?:$)|(?:$delimeter)/";
  			if (preg_match($parens_regex,$line,$m))
  			{
  				$full_match=$m[0];
  				//if parens detected - do recursion
  				while($this->Parse_Arguments($m[3], $item, $item_match, $recurent_delimeter))
  				{
  					$arg[]=$item;
  					$m[3]=trim(substr($m[3],strlen($item_match)));
  				}
  				return true;
  			}
  			elseif(preg_match($quoted_regex,$line,$m))
  			{
  				$full_match=$m[0];
				if(!preg_match('/^\w/',$m[1]))	//exclude quotes or braces
				{
					$m[1]=substr($m[1],1,strlen($m[1])-2);
				}
  				$arg=$m[1];
  				return true;
  			}
  			return false;
  		}
  		
  		function Parse($line)
  		{
  			$initial_line = $line;
  			$result = array(type=>'op', op=>'echo', operands=>array($initial_line), dont_parse=>true); //by default (if parse failed) - line would be echoed
  			
			//parses text line into template record
			//remove comments and leading/trailing whitespaces
			preg_replace('/#.*$/','', $line);
			$line = trim($line);
			
			//try get operator and operands 		
			if (preg_match('/^
			(_)?							#1-Same line modifier
			([<>%!][<>+*]?)			#2-Operator
			(?:							# Attributes
				\/([*,|]+)				#3-Actual attributes	
				(?:						# "Array to String Delimeter" option
					\[(.*?)\]			#4-Delimeter Itself
				)?
			)?
			\s
			/x', $line, $m))
			{
				$operands = trim(substr($line,strlen($m[0])));
				$t = array(operands=>array($operands));
				$entry=true;
				$button=false;
				//operation parsing
				switch(trim($m[2]))
				{
					case ">": 	$entry=false; $t['op'] = 'echo'; break;
					case ">>": 	$entry=false; $t['op'] = 'paragraph';	break;
					case ">+":	$entry=false; $t['op'] = 'comments'; break;
					case "!>": 	$button=true; $t['button']='submit'; break;
					case "!*": 	$button=true; $t['button']='reset'; break;
					case "!+":	$button=true; $t['button']='button'; break;
					case "<":	$t['op'] = 'entry_field_text'; break;
					case "<<":	$t['op'] = 'entry_free_text'; break;
					case "<*":	$t['op'] = 'entry_password'; break;
					case "%":	$t['op'] = 'entry_radio'; break;
					case "%+":	$t['op'] = 'entry_checkbox'; break;
					case "^":	$t['op'] = 'entry_combo'; break;
					case "^+":	$t['op'] = 'entry_combo'; 
									$t['attributes']['multiple'] = true;
									break;
					case "^<":	$t['op'] = 'entry_agregating_combo'; break;
					default :	$t=NULL;
				}
				if ($t)
				{
					//distinguish button/operation
					if($button)
					{ $t['type']='button'; }
					else
					{ $t['type']='op'; }
					
					//attributes
					if($m[1])
					{ $t['attributes']['no_newline']=true; }
					
					if($button)
					{}
					elseif($entry)
					{
						if (strpos($m[3],'*'))
						{ $t['attributes']['required']=true; }
						if (strpos($m[3],','))
						{ $t['attributes']['delimeter']=$m[4]; }
					}
					else
					{
						if (strpos($m[3],'|'))
						{ $t['attributes']['column']=true; }
						if (strpos($m[3],','))
						{ $t['attributes']['delimeter']=$m[4]; }
					}
					
					//separate operands for button and entry
					if (($entry) or ($button))
					{
						$o=array();
						$regex='/^\s*'.self::quoted_regex.'/';
						while(
  		function Parse_Arguments($line, &$arg, &$full_match, $delimeter='\s', $recurent_delimeter=',')
)
						{
							$o[]=$arg;
							//exclude $full_match from $line 
						}
						
						if(strlen($operands))	//Put unparsed part into operands
						{ $o[]=$operands; }
						
						$t['operands']=$o;
					}
				}
				
				if($t)
				{	$result = $t;
					$this->Debug_Parsed[] = "Operation:".var_export($t,true);
				}
				else
				{
					$this->Debug_Unparsed[] = $initial_line;
				}
			
				return $result;
			}
			
			//try get group
			if (preg_match('/^
				:\s*"						#Group identifier and opening double-quotes
				(.*?)						#1-Group name
				(?<!\\)"					#Closing by unescaped double-qoute 
				/x', $line, $m))
			{
				$this->Debug_Parsed[] = "Group:$initial_line";
				return $result;
			}			
			
			//try get script
			if (preg_match('/^
				&\s*"						#Script identifier and opening double-quotes
				(.*?)						#1-Script body or script file name
				(?:						# Script file offsets
					\(
					(.*?):				#2-Start offset
					(.*?)					#3-End offset
					\)
				)?
				(?<!\\)"					#Closing by unescaped double-qoute 
				/x', $line, $m))
			{
				$this->Debug_Parsed[] = "Script:$initial_line";
				return $result;
			}
			
			//try get properties
			if (preg_match('/
			@\s*
			(
			\(							#Opening parentheses
			.*?
			(?<!\\)\)				#Unescaped Closing parentheses
			)
			/x', $line, $m))
			{
				//unescape closing parentheses
				//evaluate
				$this->Debug_Parsed[] = "Properties:$initial_line";
				return $result;
			}
			
			//try get alias
			if (preg_match('/^
			\$(\w+)						#1-Alias name
			\s* = \s?\s*
			(.*?)							#2-Alias value
			\s?\s*$
			/x', $line, $m))
			{
				$this->Debug_Parsed[] = "Alias:$initial_line";
				return $result;
			}
			
			$this->Debug_Unparsed[] = $initial_line;
			return $result;
  		}
  		
  		function FetchValues($rows)
  		{
  			//fetch values from given DB row(s) into parsed template (clone actualy)
  			//rows - cause there could be fields from different tables
  		}
  		
  		function UpdateValues($rows)
  		{
  			//fills values from template structure into given DB row(s)
  			//rows - cause there could be fields from different tables
  		}
  }

?>